package device

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"errors"
	"fmt"
	"time"

	"iot-cloud-platform/internal/database"
	"iot-cloud-platform/internal/models"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

var (
	ErrDeviceNotFound      = errors.New("device not found")
	ErrDeviceAlreadyExists = errors.New("device already exists")
	ErrInvalidDeviceData   = errors.New("invalid device data")
	ErrDeviceOffline       = errors.New("device is offline")
)

// DeviceService handles device management operations
type DeviceService struct {
	db    *gorm.DB
	redis *database.RedisManager
}

// NewDeviceService creates a new device service
func NewDeviceService(db *gorm.DB, redis *database.RedisManager) *DeviceService {
	return &DeviceService{
		db:    db,
		redis: redis,
	}
}

// CreateDeviceRequest represents a device creation request
type CreateDeviceRequest struct {
	ProductKey  string                `json:"product_key" binding:"required"`
	DeviceName  string                `json:"device_name" binding:"required"`
	Description string                `json:"description"`
	Location    *models.Location      `json:"location"`
	Metadata    models.DeviceMetadata `json:"metadata"`
	Tags        []string              `json:"tags"`
}

// UpdateDeviceRequest represents a device update request
type UpdateDeviceRequest struct {
	Description string                `json:"description"`
	Status      models.DeviceStatus   `json:"status"`
	Location    *models.Location      `json:"location"`
	Metadata    models.DeviceMetadata `json:"metadata"`
}

// DeviceListRequest represents device listing parameters
type DeviceListRequest struct {
	Page       int                 `form:"page,default=1"`
	PageSize   int                 `form:"page_size,default=20"`
	Status     models.DeviceStatus `form:"status"`
	ProductKey string              `form:"product_key"`
	Search     string              `form:"search"`
	Tags       []string            `form:"tags"`
}

// DeviceListResponse represents device listing response
type DeviceListResponse struct {
	Devices  []models.Device `json:"devices"`
	Total    int64           `json:"total"`
	Page     int             `json:"page"`
	PageSize int             `json:"page_size"`
}

// CreateDevice creates a new device
func (ds *DeviceService) CreateDevice(ctx context.Context, req CreateDeviceRequest, ownerID uuid.UUID) (*models.Device, error) {
	// Check if device already exists
	var existingDevice models.Device
	if err := ds.db.Where("product_key = ? AND device_name = ?", req.ProductKey, req.DeviceName).First(&existingDevice).Error; err == nil {
		return nil, ErrDeviceAlreadyExists
	}

	// Generate device secret
	deviceSecret, err := generateDeviceSecret()
	if err != nil {
		return nil, fmt.Errorf("failed to generate device secret: %w", err)
	}

	// Create device
	device := models.Device{
		ProductKey:   req.ProductKey,
		DeviceName:   req.DeviceName,
		DeviceSecret: deviceSecret,
		Description:  req.Description,
		Status:       models.DeviceStatusInactive,
		Location:     req.Location,
		Metadata:     req.Metadata,
		OwnerID:      ownerID,
	}

	if err := ds.db.Create(&device).Error; err != nil {
		return nil, fmt.Errorf("failed to create device: %w", err)
	}

	// Create device shadow
	shadow := models.DeviceShadow{
		DeviceID: device.ID,
		Desired:  make(models.ShadowState),
		Reported: make(models.ShadowState),
		Delta:    make(models.ShadowState),
		Version:  1,
	}

	if err := ds.db.Create(&shadow).Error; err != nil {
		return nil, fmt.Errorf("failed to create device shadow: %w", err)
	}

	// Create device credentials
	credentials := models.DeviceCredentials{
		DeviceID: device.ID,
		CredType: "secret",
		Secret:   deviceSecret,
		IsActive: true,
	}

	if err := ds.db.Create(&credentials).Error; err != nil {
		return nil, fmt.Errorf("failed to create device credentials: %w", err)
	}

	// Load the complete device with associations
	if err := ds.db.Preload("Owner").First(&device, device.ID).Error; err != nil {
		return nil, fmt.Errorf("failed to load created device: %w", err)
	}

	return &device, nil
}

// GetDevice retrieves a device by ID
func (ds *DeviceService) GetDevice(ctx context.Context, deviceID uuid.UUID) (*models.Device, error) {
	var device models.Device
	if err := ds.db.Preload("Owner").First(&device, "id = ?", deviceID).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrDeviceNotFound
		}
		return nil, fmt.Errorf("failed to get device: %w", err)
	}

	// Get device status from cache
	status, err := ds.redis.GetDeviceStatus(ctx, deviceID.String())
	if err == nil && status != "" {
		device.Status = models.DeviceStatus(status)
	}

	// Get last seen from cache
	lastSeen, err := ds.redis.GetDeviceLastSeen(ctx, deviceID.String())
	if err == nil && lastSeen > 0 {
		lastSeenTime := time.Unix(lastSeen, 0)
		device.LastSeen = &lastSeenTime
	}

	return &device, nil
}

// GetDeviceByProductKeyAndName retrieves a device by product key and device name
func (ds *DeviceService) GetDeviceByProductKeyAndName(ctx context.Context, productKey, deviceName string) (*models.Device, error) {
	var device models.Device
	if err := ds.db.Where("product_key = ? AND device_name = ?", productKey, deviceName).First(&device).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrDeviceNotFound
		}
		return nil, fmt.Errorf("failed to get device: %w", err)
	}

	return &device, nil
}

// UpdateDevice updates device information
func (ds *DeviceService) UpdateDevice(ctx context.Context, deviceID uuid.UUID, req UpdateDeviceRequest) (*models.Device, error) {
	var device models.Device
	if err := ds.db.First(&device, "id = ?", deviceID).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrDeviceNotFound
		}
		return nil, fmt.Errorf("failed to get device: %w", err)
	}

	// Update fields
	updates := make(map[string]interface{})
	if req.Description != "" {
		updates["description"] = req.Description
	}
	if req.Status != "" {
		updates["status"] = req.Status
		// Update cache as well
		ds.redis.SetDeviceStatus(ctx, deviceID.String(), string(req.Status), 3600)
	}
	if req.Location != nil {
		updates["location"] = req.Location
	}
	if len(req.Metadata.Properties) > 0 || req.Metadata.Manufacturer != "" {
		updates["metadata"] = req.Metadata
	}

	if len(updates) > 0 {
		if err := ds.db.Model(&device).Updates(updates).Error; err != nil {
			return nil, fmt.Errorf("failed to update device: %w", err)
		}
	}

	// Reload device with associations
	if err := ds.db.Preload("Owner").First(&device, device.ID).Error; err != nil {
		return nil, fmt.Errorf("failed to reload device: %w", err)
	}

	return &device, nil
}

// DeleteDevice deletes a device (soft delete)
func (ds *DeviceService) DeleteDevice(ctx context.Context, deviceID uuid.UUID) error {
	var device models.Device
	if err := ds.db.First(&device, "id = ?", deviceID).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return ErrDeviceNotFound
		}
		return fmt.Errorf("failed to get device: %w", err)
	}

	// Soft delete the device
	if err := ds.db.Delete(&device).Error; err != nil {
		return fmt.Errorf("failed to delete device: %w", err)
	}

	// Clean up cache
	ds.redis.DeleteTemporaryData(ctx, fmt.Sprintf("device:status:%s", deviceID.String()))
	ds.redis.DeleteTemporaryData(ctx, fmt.Sprintf("device:lastseen:%s", deviceID.String()))
	ds.redis.DeleteTemporaryData(ctx, fmt.Sprintf("device:shadow:%s", deviceID.String()))

	return nil
}

// ListDevices returns a paginated list of devices
func (ds *DeviceService) ListDevices(ctx context.Context, req DeviceListRequest, ownerID uuid.UUID) (*DeviceListResponse, error) {
	query := ds.db.Model(&models.Device{}).Where("owner_id = ?", ownerID)

	// Apply filters
	if req.Status != "" {
		query = query.Where("status = ?", req.Status)
	}
	if req.ProductKey != "" {
		query = query.Where("product_key = ?", req.ProductKey)
	}
	if req.Search != "" {
		query = query.Where("device_name ILIKE ? OR description ILIKE ?", "%"+req.Search+"%", "%"+req.Search+"%")
	}

	// Count total records
	var total int64
	if err := query.Count(&total).Error; err != nil {
		return nil, fmt.Errorf("failed to count devices: %w", err)
	}

	// Apply pagination
	offset := (req.Page - 1) * req.PageSize
	query = query.Offset(offset).Limit(req.PageSize)

	// Fetch devices
	var devices []models.Device
	if err := query.Preload("Owner").Order("created_at DESC").Find(&devices).Error; err != nil {
		return nil, fmt.Errorf("failed to fetch devices: %w", err)
	}

	return &DeviceListResponse{
		Devices:  devices,
		Total:    total,
		Page:     req.Page,
		PageSize: req.PageSize,
	}, nil
}

// GetDeviceShadow retrieves device shadow
func (ds *DeviceService) GetDeviceShadow(ctx context.Context, deviceID uuid.UUID) (*models.DeviceShadow, error) {
	var shadow models.DeviceShadow
	if err := ds.db.Where("device_id = ?", deviceID).First(&shadow).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrDeviceNotFound
		}
		return nil, fmt.Errorf("failed to get device shadow: %w", err)
	}

	return &shadow, nil
}

// UpdateDeviceShadow updates device shadow state
func (ds *DeviceService) UpdateDeviceShadow(ctx context.Context, deviceID uuid.UUID, desired, reported models.ShadowState) (*models.DeviceShadow, error) {
	var shadow models.DeviceShadow
	if err := ds.db.Where("device_id = ?", deviceID).First(&shadow).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrDeviceNotFound
		}
		return nil, fmt.Errorf("failed to get device shadow: %w", err)
	}

	// Update shadow state
	if desired != nil {
		shadow.Desired = desired
	}
	if reported != nil {
		shadow.Reported = reported
	}

	// Calculate delta (desired - reported)
	shadow.Delta = calculateShadowDelta(shadow.Desired, shadow.Reported)
	shadow.Version++

	if err := ds.db.Save(&shadow).Error; err != nil {
		return nil, fmt.Errorf("failed to update device shadow: %w", err)
	}

	// Cache the shadow
	ds.cacheShadow(ctx, deviceID, &shadow)

	return &shadow, nil
}

// SetDeviceOnline marks device as online
func (ds *DeviceService) SetDeviceOnline(ctx context.Context, deviceID uuid.UUID) error {
	// Update database status
	if err := ds.db.Model(&models.Device{}).Where("id = ?", deviceID).Updates(map[string]interface{}{
		"status":    models.DeviceStatusActive,
		"last_seen": time.Now(),
	}).Error; err != nil {
		return fmt.Errorf("failed to update device status: %w", err)
	}

	// Update cache
	ds.redis.SetDeviceStatus(ctx, deviceID.String(), string(models.DeviceStatusActive), 3600)
	ds.redis.SetDeviceLastSeen(ctx, deviceID.String(), time.Now().Unix())

	return nil
}

// SetDeviceOffline marks device as offline
func (ds *DeviceService) SetDeviceOffline(ctx context.Context, deviceID uuid.UUID) error {
	// Update database status
	if err := ds.db.Model(&models.Device{}).Where("id = ?", deviceID).Update("status", models.DeviceStatusOffline).Error; err != nil {
		return fmt.Errorf("failed to update device status: %w", err)
	}

	// Update cache
	ds.redis.SetDeviceStatus(ctx, deviceID.String(), string(models.DeviceStatusOffline), 3600)

	return nil
}

// GetDeviceCredentials retrieves device authentication credentials
func (ds *DeviceService) GetDeviceCredentials(ctx context.Context, deviceID uuid.UUID) (*models.DeviceCredentials, error) {
	var credentials models.DeviceCredentials
	if err := ds.db.Where("device_id = ? AND is_active = true", deviceID).First(&credentials).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrDeviceNotFound
		}
		return nil, fmt.Errorf("failed to get device credentials: %w", err)
	}

	return &credentials, nil
}

// ValidateDeviceCredentials validates device authentication
func (ds *DeviceService) ValidateDeviceCredentials(ctx context.Context, productKey, deviceName, deviceSecret string) (*models.Device, error) {
	var device models.Device
	if err := ds.db.Where("product_key = ? AND device_name = ? AND device_secret = ?", productKey, deviceName, deviceSecret).First(&device).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrDeviceNotFound
		}
		return nil, fmt.Errorf("failed to validate device credentials: %w", err)
	}

	return &device, nil
}

// RegenerateDeviceSecret regenerates device secret
func (ds *DeviceService) RegenerateDeviceSecret(ctx context.Context, deviceID uuid.UUID) (*models.Device, error) {
	var device models.Device
	if err := ds.db.First(&device, "id = ?", deviceID).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrDeviceNotFound
		}
		return nil, fmt.Errorf("failed to get device: %w", err)
	}

	// Generate new secret
	newSecret, err := generateDeviceSecret()
	if err != nil {
		return nil, fmt.Errorf("failed to generate device secret: %w", err)
	}

	// Update device secret
	if err := ds.db.Model(&device).Update("device_secret", newSecret).Error; err != nil {
		return nil, fmt.Errorf("failed to update device secret: %w", err)
	}

	// Update credentials
	if err := ds.db.Model(&models.DeviceCredentials{}).Where("device_id = ?", deviceID).Update("secret", newSecret).Error; err != nil {
		return nil, fmt.Errorf("failed to update device credentials: %w", err)
	}

	// Reload device
	if err := ds.db.First(&device, device.ID).Error; err != nil {
		return nil, fmt.Errorf("failed to reload device: %w", err)
	}

	return &device, nil
}

// Helper functions

// generateDeviceSecret generates a secure random device secret
func generateDeviceSecret() (string, error) {
	bytes := make([]byte, 32)
	if _, err := rand.Read(bytes); err != nil {
		return "", err
	}
	return hex.EncodeToString(bytes), nil
}

// calculateShadowDelta calculates the difference between desired and reported state
func calculateShadowDelta(desired, reported models.ShadowState) models.ShadowState {
	delta := make(models.ShadowState)

	for key, desiredValue := range desired {
		if reportedValue, exists := reported[key]; !exists || reportedValue != desiredValue {
			delta[key] = desiredValue
		}
	}

	return delta
}

// cacheShadow caches device shadow in Redis
func (ds *DeviceService) cacheShadow(ctx context.Context, deviceID uuid.UUID, shadow *models.DeviceShadow) {
	// In a real implementation, you'd serialize the shadow to JSON and cache it
	// For simplicity, we'll skip the implementation here
}
